package es.deusto.programacionIV.calculator.model;

import java.util.Collection;
import java.util.Iterator;

import es.deusto.programacionIV.utils.JarUtils;

/**
 * This class simplifies the process of initializing and using a calculator of different operation types.
 * It also uses an OperationDataSource internally hiding its existence to the final user.
 * @author Iker Jamardo Zugaza
 *
 */
public class CalculatorWithDataSource
{
	public static final String ATTRIBUTE_NAME_OPERATION_DATA_SOURCE_CLASS_NAME = "OperationDataSource"; 
	private Collection<OperationType> operationTypes = null;
	private OperationDataSource operationDataSource = null;
	private float operand1 = 0;
	private float operand2 = 0;
	private Operation operation = null;
	private boolean initialized = false;
	
	private void checkInitialized()
	{
		if (!initialized)
		{
			throw new IllegalStateException("The CalculatorWithDataSource is not initialized");
		}
	}
	
	/**
	 * Initializes the calculator registering the operation types in a specific order and initializng the operation
	 * data source.
	 * @param operationTypeJarFilesDirectoryPath The path to the directory where the operation type jar files are stored.
	 * @param operationClassNamesTXTFilePath The path to the TXT file where the operation type class names are stored.
	 * @param operationDataSourceJarFilePath The path to the jar file where the operation data source class implementation is stored.
	 * @throws Exception If tghere is any kind of error while initializing the calculator.
	 */
	public void init(String operationTypeJarFilesDirectoryPath, String operationClassNamesTXTFilePath, String operationDataSourceJarFilePath) throws Exception
	{
		if (initialized)
		{
			throw new IllegalStateException("Trying to initialize an already initialized CalculatorWithDataSource");
		}
		operationTypes = JarUtils.instantiateObjectsFromJarFilesWithClassNameTXTFile(operationTypeJarFilesDirectoryPath, operationClassNamesTXTFilePath);
		operationDataSource = JarUtils.instantiateObjectFromJarFilePathAndManifestAttribute(operationDataSourceJarFilePath, ATTRIBUTE_NAME_OPERATION_DATA_SOURCE_CLASS_NAME);
		operationDataSource.init(operationTypes);
		operation = null;
		operand1 = 0;
		operand2 = 0;
		initialized = true;
	}
	
	/**
	 * Ends the calculator.
	 * @throws Exception If there is any kind of error while ending the calculator.
	 */
	public void end() throws Exception
	{
		if (!initialized)
		{
			throw new IllegalStateException("Trying to end a non initialized CalculatorWithDataSource.");
		}
		operationDataSource.end();
		operationDataSource = null;
		operationTypes = null;
		operation = null;
		operand1 = 0;
		operand2 = 0;
		initialized = false;
	}
	
	/**
	 * Returns all the calculated operations obtained from the operation data source.
	 * @return All the calculated operations obtained from the operation data source.
	 * @throws Exception If there is any kind of error while retrieving all the calculated operations.
	 */
	public Collection<Operation> getAllCalculatedOperations() throws Exception
	{
		checkInitialized();
		return operationDataSource.getAllCalculatedOperations();
	}
	
	/**
	 * Returns the names of the available operation types.
	 * @return
	 */
	public String[] getOperationTypeNames()
	{
		checkInitialized();
		String[] operationTypeNamesArray = new String[operationTypes.size()];
		int i = 0;
		for (OperationType operationType: operationTypes)
		{
			operationTypeNamesArray[i++] = operationType.getName();
		}
		return operationTypeNamesArray;
	}

	/**
	 * Sets the type of operation to be performed.
	 * @param index The index of the operation type to be selected.
	 */
	public void setOperationTypeIndex(int index)
	{
		checkInitialized();
		if (index < 0 || index >= operationTypes.size())
		{
			throw new IllegalArgumentException("The given index is out of operation type range (0-" + operationTypes.size() + ")");
		}
		Iterator<OperationType> operationTypesIterator = operationTypes.iterator();
		for (int i = 0; i < index; i++) operationTypesIterator.next();
		operation = operationTypesIterator.next().createOperation();
	}
	
	/**
	 * Calculates the selected operation and returns the result.
	 * @return The result of calculating the selected operation.
	 * @throws Exception If there is any kind of error while calculating the selected operation.
	 */
	public float calculate() throws Exception
	{
		checkInitialized();
		if (operation == null)
		{
			throw new IllegalStateException("Cannot calculate if there is no operation set");
		}
		operation.setOperand1(operand1);
		operation.setOperand2(operand2);
		operation.calculate();
		operationDataSource.insertCalculatedOperation(operation);
		operand1 = operation.getResult();
		return operand1;
	}
	
	/**
	 * Sets the operand 1.
	 * @param operand1
	 */
	public void setOperand1(float operand1)
	{
		checkInitialized();
		this.operand1 = operand1;
	}
	
	/**
	 * Sets the operand2.
	 * @param operand2
	 */
	public void setOperand2(float operand2)
	{
		checkInitialized();
		this.operand2 = operand2;
	}
	
	/**
	 * Returns the string that represents the selected operation.
	 * @return The string that represents the selected operation.
	 */
	public String getOperationToString()
	{
		checkInitialized();
		if (operation == null)
		{
			throw new IllegalStateException("Cannot get the operation string if there is no operation set");
		}
		return operation.toString();
	}
}
